/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006-2009 Robert Beckebans <trebor_7@users.sourceforge.net>

This file is part of XreaL source code.

XreaL source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

XreaL source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with XreaL source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// tr_image.c
#include "tr_local.h"

#include "../png/png.h"



/*
=========================================================

PNG LOADING

=========================================================
*/
static void png_read_data(png_structp png, png_bytep data, png_size_t length)
{
	Com_Memcpy(data, png->io_ptr, length);

	// raynorpat: msvc is gay
#if _MSC_VER
	(byte *) png->io_ptr += length;
#else
	png->io_ptr += length;
#endif
}

static void png_user_warning_fn(png_structp png_ptr, png_const_charp warning_message)
{
	ri.Printf(PRINT_WARNING, "libpng warning: %s\n", warning_message);
}

static void png_user_error_fn(png_structp png_ptr, png_const_charp error_message)
{
	ri.Printf(PRINT_ERROR, "libpng error: %s\n", error_message);
	longjmp(png_ptr->jmpbuf, 0);
}

void LoadPNG(const char *name, byte ** pic, int *width, int *height, byte alphaByte)
{
	int             bit_depth;
	int             color_type;
	png_uint_32     w;
	png_uint_32     h;
	unsigned int    row;
	size_t          rowbytes;
	png_infop       info;
	png_structp     png;
	png_bytep      *row_pointers;
	byte           *data;
	byte           *out;
	int             size;

	// load png
	size = ri.FS_ReadFile(name, (void **)&data);

	if(!data)
		return;

	//png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	png = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp) NULL, png_user_error_fn, png_user_warning_fn);

	if(!png)
	{
		ri.Printf(PRINT_WARNING, "LoadPNG: png_create_write_struct() failed for (%s)\n", name);
		ri.FS_FreeFile(data);
		return;
	}

	// allocate/initialize the memory for image information.  REQUIRED
	info = png_create_info_struct(png);
	if(!info)
	{
		ri.Printf(PRINT_WARNING, "LoadPNG: png_create_info_struct() failed for (%s)\n", name);
		ri.FS_FreeFile(data);
		png_destroy_read_struct(&png, (png_infopp) NULL, (png_infopp) NULL);
		return;
	}

	/*
	 * Set error handling if you are using the setjmp/longjmp method (this is
	 * the normal method of doing things with libpng).  REQUIRED unless you
	 * set up your own error handlers in the png_create_read_struct() earlier.
	 */
	if(setjmp(png_jmpbuf(png)))
	{
		// if we get here, we had a problem reading the file
		ri.Printf(PRINT_WARNING, "LoadPNG: first exception handler called for (%s)\n", name);
		ri.FS_FreeFile(data);
		png_destroy_read_struct(&png, (png_infopp) & info, (png_infopp) NULL);
		return;
	}

	//png_set_write_fn(png, buffer, png_write_data, png_flush_data);
	png_set_read_fn(png, data, png_read_data);

	png_set_sig_bytes(png, 0);

	// The call to png_read_info() gives us all of the information from the
	// PNG file before the first IDAT (image data chunk).  REQUIRED
	png_read_info(png, info);

	// get picture info
	png_get_IHDR(png, info, (png_uint_32 *) & w, (png_uint_32 *) & h, &bit_depth, &color_type, NULL, NULL, NULL);

	// tell libpng to strip 16 bit/color files down to 8 bits/color
	png_set_strip_16(png);

	// expand paletted images to RGB triplets
	if(color_type & PNG_COLOR_MASK_PALETTE)
		png_set_expand(png);

	// expand gray-scaled images to RGB triplets
	if(!(color_type & PNG_COLOR_MASK_COLOR))
		png_set_gray_to_rgb(png);

	// expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel
	//if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
	//  png_set_gray_1_2_4_to_8(png);

	// expand paletted or RGB images with transparency to full alpha channels
	// so the data will be available as RGBA quartets
	if(png_get_valid(png, info, PNG_INFO_tRNS))
		png_set_tRNS_to_alpha(png);

	// if there is no alpha information, fill with alphaByte
	if(!(color_type & PNG_COLOR_MASK_ALPHA))
		png_set_filler(png, alphaByte, PNG_FILLER_AFTER);

	// expand pictures with less than 8bpp to 8bpp
	if(bit_depth < 8)
		png_set_packing(png);

	// update structure with the above settings
	png_read_update_info(png, info);

	// allocate the memory to hold the image
	*width = w;
	*height = h;
	*pic = out = (byte *) ri.Z_Malloc(w * h * 4);

	row_pointers = (png_bytep *) ri.Hunk_AllocateTempMemory(sizeof(png_bytep) * h);

	// set a new exception handler
	if(setjmp(png_jmpbuf(png)))
	{
		ri.Printf(PRINT_WARNING, "LoadPNG: second exception handler called for (%s)\n", name);
		ri.Hunk_FreeTempMemory(row_pointers);
		ri.FS_FreeFile(data);
		png_destroy_read_struct(&png, (png_infopp) & info, (png_infopp) NULL);
		return;
	}

	rowbytes = png_get_rowbytes(png, info);

	for(row = 0; row < h; row++)
		row_pointers[row] = (png_bytep) (out + (row * 4 * w));

	// read image data
	png_read_image(png, row_pointers);

	// read rest of file, and get additional chunks in info
	png_read_end(png, info);

	// clean up after the read, and free any memory allocated
	png_destroy_read_struct(&png, &info, (png_infopp) NULL);

	ri.Hunk_FreeTempMemory(row_pointers);
	ri.FS_FreeFile(data);
}

/*
=========================================================

PNG SAVING

=========================================================
*/
static int      png_compressed_size;

static void png_write_data(png_structp png, png_bytep data, png_size_t length)
{
	memcpy(png->io_ptr, data, length);

	// raynorpat: msvc is gay
#if _MSC_VER
	(byte *) png->io_ptr += length;
#else
	png->io_ptr += length;
#endif

	png_compressed_size += length;
}

static void png_flush_data(png_structp png)
{
}

void SavePNG(const char *name, const byte * pic, int width, int height, int numBytes, qboolean flip)
{
	png_structp     png;
	png_infop       info;
	int             i;
	int             row_stride;
	byte           *buffer;
	byte           *row;
	png_bytep      *row_pointers;

	png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

	if(!png)
		return;

	// Allocate/initialize the image information data
	info = png_create_info_struct(png);
	if(!info)
	{
		png_destroy_write_struct(&png, (png_infopp) NULL);
		return;
	}

	png_compressed_size = 0;
	buffer = ri.Hunk_AllocateTempMemory(width * height * numBytes);

	// set error handling
	if(setjmp(png_jmpbuf(png)))
	{
		ri.Hunk_FreeTempMemory(buffer);
		png_destroy_write_struct(&png, &info);
		return;
	}

	png_set_write_fn(png, buffer, png_write_data, png_flush_data);

	if(numBytes == 4)
	{
		png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
				 PNG_FILTER_TYPE_DEFAULT);
	}
	else
	{
		// should be 3
		png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
				PNG_FILTER_TYPE_DEFAULT);
	}

	// write the file header information
	png_write_info(png, info);

	row_pointers = ri.Hunk_AllocateTempMemory(height * sizeof(png_bytep));

	if(setjmp(png_jmpbuf(png)))
	{
		ri.Hunk_FreeTempMemory(row_pointers);
		ri.Hunk_FreeTempMemory(buffer);
		png_destroy_write_struct(&png, &info);
		return;
	}

	row_stride = width * numBytes;
	row = (byte *)pic + (height - 1) * row_stride;

	if(flip)
	{
		for(i = height - 1; i >= 0; i--)
		{
			row_pointers[i] = row;
			row -= row_stride;
		}
	}
	else
	{
		for(i = 0; i < height; i++)
		{
			row_pointers[i] = row;
			row -= row_stride;
		}
	}

	png_write_image(png, row_pointers);
	png_write_end(png, info);

	// clean up after the write, and free any memory allocated
	png_destroy_write_struct(&png, &info);

	ri.Hunk_FreeTempMemory(row_pointers);

	ri.FS_WriteFile(name, buffer, png_compressed_size);

	ri.Hunk_FreeTempMemory(buffer);
}




